{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# MoA(Mixture of Agents) Search\n",
    "\n",
    "- Author: [Kane](https://github.com/HarryKane11)\n",
    "- Peer Review: \n",
    "- Proofread : [JaeJun Shim](https://github.com/kkam-dragon)\n",
    "- This is a part of [LangChain Open Tutorial](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial)\n",
    "\n",
    "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/19-Cookbook/07-Agent/14-MoARAG.ipynb)[![Open in GitHub](https://img.shields.io/badge/Open%20in%20GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/19-Cookbook/07-Agent/14-MoARAG.ipynb)\n",
    "## Overview\n",
    "\n",
    "This code implements a sophisticated Multi-Model Search and Analysis System designed to provide comprehensive answers to user queries through a distributed search and analysis workflow.\n",
    "The system architecture consists of three main components:\n",
    "\n",
    "- Query Generation and Search\n",
    "  - The system generates multiple search queries from the user's question using GPT-4o, ensuring comprehensive coverage of the topic. These queries are then executed in parallel using the Tavily Search API for efficient information gathering.\n",
    "- Multi-Model Analysis\n",
    "  - The aggregated search results are processed independently by three different language models (GPT-4o Mini, Claude Haiku, and Gemini 1.5 Flash 8B). Each model provides its unique perspective and analysis, complete with citations to source materials.\n",
    "- Result Synthesis\n",
    "- The system collects analyses from all models and synthesizes them using GPT-4o, producing a comprehensive response that incorporates diverse viewpoints while maintaining citation accuracy.\n",
    "\n",
    "**Key Features**:\n",
    "\n",
    "- Parallel Processing: Implements a map-reduce pattern for efficient search execution and analysis distribution across multiple models.\n",
    "- Multi-Model Architecture: Leverages three distinct AI models to analyze search results from different perspectives, ensuring a balanced and thorough analysis.\n",
    "- Dynamic Workflow: Built on LangGraph's state management system, enabling real-time result streaming and flexible execution paths while maintaining robust state management throughout the process.\n",
    "\n",
    "This system is particularly effective for complex queries requiring comprehensive information gathering and analysis, leveraging the strengths of multiple AI models to provide thorough and balanced responses.\n",
    "\n",
    "### Table of Contents\n",
    "\n",
    "- [Overview](#overview)\n",
    "- [Environment Setup](#environment-setup)\n",
    "- [State and Models Setup](#state-and-models-setup)\n",
    "- [Query Generation](#query-generation)\n",
    "- [Search Task Creation](#search-task-creation)\n",
    "- [Aggregating Search Results](#aggregating-search-results)\n",
    "- [Analysis Task Creation](#analysis-task-creation)\n",
    "- [Model Analysis](#model-analysis)\n",
    "- [Synthesizing Results](#synthesizing-results)\n",
    "- [Helper Functions and Execution](#helper-functions-and-execution)\n",
    "\n",
    "### References\n",
    "\n",
    "- [Genspark - Enhancements in Mixture of Agents](https://www.genspark.ai/spark/enhancements-in-mixture-of-agents/073b2415-5101-42b0-abe4-59f62410c8f0)\n",
    "- [Langgraph - Map & Reduce](https://langchain-ai.github.io/langgraph/how-tos/map-reduce/)\n",
    "- [Tavily](https://tavily.com/)\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Environment Setup\n",
    "\n",
    "Set up the environment. You may refer to [Environment Setup](https://wikidocs.net/257836) for more details.\n",
    "\n",
    "**[Note]**\n",
    "\n",
    "- ```langchain-opentutorial``` is a package that provides a set of easy-to-use environment setup, useful functions and utilities for tutorials.\n",
    "- You can checkout the [```langchain-opentutorial```](https://github.com/LangChain-OpenTutorial/langchain-opentutorial-pypi) for more details.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n",
      "[notice] A new release of pip is available: 24.3.1 -> 25.0\n",
      "[notice] To update, run: python.exe -m pip install --upgrade pip\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: langchain-opentutorial in c:\\users\\gram\\appdata\\local\\pypoetry\\cache\\virtualenvs\\langchain-opentutorial-rxtdr8w5-py3.11\\lib\\site-packages (0.0.3)\n"
     ]
    }
   ],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install langchain-opentutorial"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install required packages\n",
    "from langchain_opentutorial import package\n",
    "\n",
    "package.install(\n",
    "    [\n",
    "        \"langchain-community\",\n",
    "        \"langchain-openai\",\n",
    "        \"langchain-anthropic\",\n",
    "        \"langchain-google-genai\",\n",
    "        \"langgraph\"\n",
    "    ],\n",
    "    verbose=False,\n",
    "    upgrade=False,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Environment variables have been set successfully.\n"
     ]
    }
   ],
   "source": [
    "# Set environment variables\n",
    "\n",
    "from langchain_opentutorial import set_env\n",
    "\n",
    "set_env(\n",
    "    {\n",
    "        \"LANGCHAIN_API_KEY\": \"\",\n",
    "        \"LANGCHAIN_TRACING_V2\": \"true\",\n",
    "        \"LANGCHAIN_ENDPOINT\": \"https://api.smith.langchain.com\",\n",
    "        \"LANGCHAIN_PROJECT\": \"07-Agent\",\n",
    "        \"OPENAI_API_KEY\":\"\",\n",
    "        \"ANTHROPIC_API_KEY\":\"\",\n",
    "        \"GOOGLE_API_KEY\":\"\"\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from dotenv import load_dotenv\n",
    "\n",
    "load_dotenv(override=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Imports for required libraries and modules\n",
    "import operator\n",
    "from typing import Annotated, List, Sequence, Dict\n",
    "from typing_extensions import TypedDict\n",
    "from langchain_core.messages import BaseMessage, HumanMessage, AIMessage\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langchain_anthropic import ChatAnthropic\n",
    "from langchain_google_genai import ChatGoogleGenerativeAI\n",
    "from langchain_community.tools.tavily_search import TavilySearchResults\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from pydantic import BaseModel, Field\n",
    "from langgraph.constants import Send\n",
    "\n",
    "# Utility function to merge dictionaries\n",
    "def dict_merge(old_dict: dict, new_dict: dict) -> dict:\n",
    "    \"\"\"Reducer function for merging dictionaries.\"\"\"\n",
    "    if not old_dict:\n",
    "        return new_dict\n",
    "    if not new_dict:\n",
    "        return old_dict\n",
    "    return {**old_dict, **new_dict}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## State and Models Setup\n",
    "This section defines the ```AgentState``` structure for managing the workflow's state, \n",
    "along with the ```SearchQueries``` model for query generation. It also sets up the search tool \n",
    "and initializes multiple models with different characteristics."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define the agent's state using a TypedDict\n",
    "class AgentState(TypedDict):\n",
    "    messages: Annotated[Sequence[BaseMessage], operator.add]  # Message history\n",
    "    search_queries: List[str]  # List of generated search queries\n",
    "    raw_search_results: Annotated[Dict, dict_merge]  # Merged search results\n",
    "    aggregated_search_results: str  # Combined search results as a single document\n",
    "    model_responses: Annotated[Dict, dict_merge]  # Responses from different models\n",
    "    final_answer: str  # Synthesized final answer\n",
    "\n",
    "# Define a Pydantic model for search queries\n",
    "class SearchQueries(BaseModel):\n",
    "    queries: List[str] = Field(description=\"List of search queries\")\n",
    "\n",
    "# Set up the search tool\n",
    "search_tool = TavilySearchResults(max_results=3)\n",
    "\n",
    "# Configure multiple models with different characteristics\n",
    "models = {\n",
    "    \"gpt-4o-mini\": ChatOpenAI(model=\"gpt-4o-mini\"),  # OpenAI GPT-4\n",
    "    \"claude-haiku\": ChatAnthropic(model=\"claude-3-5-haiku-20241022\"),  # Anthropic Claude\n",
    "    \"gemini-1.5-flash\": ChatGoogleGenerativeAI(model=\"gemini-1.5-flash-8b\")  # Google's Gemini\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Query Generation\n",
    "This section defines a prompt template and combines it with a model to generate 3-5 \n",
    "search queries that cover different aspects of the user's question."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define a prompt template for generating search queries\n",
    "query_gen_prompt = ChatPromptTemplate.from_messages([\n",
    "    (\"system\", \"\"\"Generate 3-5 different search queries to find comprehensive information about the user's question.\n",
    "    Each query should focus on a different aspect or use different search terms to ensure broad coverage.\"\"\"),\n",
    "    (\"human\", \"{question}\")\n",
    "])\n",
    "\n",
    "# Combine the prompt with a model to generate queries\n",
    "query_generator = query_gen_prompt | ChatOpenAI(model=\"gpt-4o\").with_structured_output(SearchQueries)\n",
    "\n",
    "# Async function to generate queries based on the latest user message\n",
    "async def generate_queries(state: AgentState) -> dict:\n",
    "    \"\"\"Generate search queries.\"\"\"\n",
    "    question = state[\"messages\"][-1].content  # Get the latest user message\n",
    "    result = await query_generator.ainvoke({\"question\": question})  # Generate queries\n",
    "    print(f\"Generated queries: {result.queries}\")\n",
    "    return {\"search_queries\": result.queries}  # Return the list of generated queries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "SearchQueries(queries=['Tesla business strategy analysis 2023', 'Tesla competitive advantage and business model', 'Tesla SWOT analysis latest', 'Tesla market strategy and future plans', 'Tesla growth strategy and sustainability'])"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "search_queries=query_generator.invoke(\"Is tesla's business strategy valid?\")\n",
    "search_queries"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Search Task Creation\n",
    "This section creates tasks using ```Send``` objects for executing search queries in parallel. \n",
    "Each query will be processed independently."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Function to create tasks for parallel search execution\n",
    "def create_search_tasks(state: AgentState):\n",
    "    \"\"\"Create Send objects for parallel search processing.\"\"\"\n",
    "    return [\n",
    "        Send(\"search_executor\", {\"query\": query})  # Task for each search query\n",
    "        for query in state[\"search_queries\"]  # Iterate over all generated queries\n",
    "    ]\n",
    "\n",
    "# Async function to execute a single search\n",
    "async def search_executor(state: dict) -> dict:\n",
    "    \"\"\"Execute individual search queries.\"\"\"\n",
    "    try:\n",
    "        query = state[\"query\"]  # Extract the query from the state\n",
    "        print(f\"Searching for: {query}\")\n",
    "        search_results = await search_tool.ainvoke(query)  # Use the search tool to get results\n",
    "        return {\"raw_search_results\": {query: search_results}}  # Return search results\n",
    "    except Exception as e:\n",
    "        print(f\"Search error for query '{query}': {str(e)}\")\n",
    "        return {\"raw_search_results\": {}}  # Return empty results in case of an error"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Send(node='search_executor', arg={'query': ('queries', ['Tesla business strategy analysis 2023', 'Tesla competitive advantage and business model', 'Tesla SWOT analysis latest', 'Tesla market strategy and future plans', 'Tesla growth strategy and sustainability'])})]"
      ]
     },
     "execution_count": 60,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "create_search_tasks({\"search_queries\":search_queries})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Aggregating Search Results\n",
    "This section combines raw search results from multiple queries into a single, \n",
    "structured document for easier analysis."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Async function to aggregate raw search results into a single document\n",
    "async def aggregate_search_results(state: AgentState) -> dict:\n",
    "    \"\"\"Combine multiple search results into a single document.\"\"\"\n",
    "    all_results = state.get(\"raw_search_results\", {})  # Retrieve raw search results\n",
    "\n",
    "    # Create a structured document from all search results\n",
    "    aggregated_document = []\n",
    "    for query, results in all_results.items():\n",
    "        aggregated_document.append(f\"\\n### Search Results for Query: '{query}'\\n\")\n",
    "        for idx, result in enumerate(results, 1):\n",
    "            aggregated_document.append(f\"[{idx}] Content: {result['content']}\")\n",
    "            aggregated_document.append(f\"    Source: {result['url']}\\n\")\n",
    "    \n",
    "    aggregated_text = \"\\n\".join(aggregated_document)\n",
    "    return {\"aggregated_search_results\": aggregated_text}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Analysis Task Creation\n",
    "This section creates tasks for analyzing the aggregated search results using different models. \n",
    "Each model processes the results independently."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Function to create analysis tasks for each model\n",
    "def create_analysis_tasks(state: AgentState):\n",
    "    \"\"\"Create tasks for analyzing the aggregated search results with each model.\"\"\"\n",
    "    combined_results = state.get(\"aggregated_search_results\", \"\")  # Get combined search results\n",
    "    original_question = state[\"messages\"][0].content\n",
    "    return [\n",
    "        Send(\"model_analyzer\", {\n",
    "            \"model_name\": model_name,  # Specify the model name\n",
    "            \"search_results\": combined_results,  # Provide the combined results\n",
    "            \"question\": original_question\n",
    "        })\n",
    "        for model_name in models.keys()  # Iterate over all available models\n",
    "    ]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model Analysis\n",
    "A parallel processing stage where each AI model independently analyzes the aggregated search results.\n",
    "\n",
    "**Key Components**:\n",
    "1. **Input**: Combined search results and original question\n",
    "2. **Analysis Process**: Each model (gpt-4o-mini, claude-3-5-haiku-20241022, gemini-1.5-flash-8b) performs:\n",
    "   - Comprehensive review of search results\n",
    "   - Fact extraction with citations\n",
    "   - Response formulation based on source material\n",
    "3. **Output**: Structured analysis from each model, maintaining citation integrity\n",
    "\n",
    "The goal is to leverage each model's unique strengths in comprehension and analysis while ensuring traceability back to source materials."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Async function to analyze search results using a specific model\n",
    "async def model_analyzer(state: dict) -> dict:\n",
    "    \"\"\"Perform analysis of search results with an individual model.\"\"\"\n",
    "    try:\n",
    "        model_name = state[\"model_name\"]\n",
    "        search_results = state[\"search_results\"]\n",
    "        question = state[\"question\"]\n",
    "        \n",
    "        analysis_prompt = f\"\"\"Original Question: {question}\n",
    "        Search Results:\n",
    "        {search_results}\n",
    "\n",
    "        Please provide a comprehensive answer to the original question based on these search results.\n",
    "        Requirements:\n",
    "        1. Answer the question accurately using the provided search results\n",
    "        2. Use citation numbers [1], [2], etc. when referencing specific information\n",
    "        3. Be clear and concise while maintaining accuracy\n",
    "        4. If different sources provide conflicting information, acknowledge this and explain\n",
    "\n",
    "        Format your answer as follows:\n",
    "        1. Main answer with citations\n",
    "        2. Additional relevant context (if any)\n",
    "        3. Any limitations or uncertainties in the available information\n",
    "        \"\"\"\n",
    "        \n",
    "        model = models[model_name]\n",
    "        response = await model.ainvoke([HumanMessage(content=analysis_prompt)])\n",
    "        return {\"model_responses\": {model_name: response.content}}\n",
    "    \n",
    "    except Exception as e:\n",
    "        print(f\"Analysis error for model {model_name}: {str(e)}\")\n",
    "        return {\"model_responses\": {model_name: \"Analysis failed\"}}\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Synthesizing Results\n",
    "The final stage of our multi-model analysis pipeline, combining diverse AI perspectives into a single coherent response.\n",
    "\n",
    "**Key Components**:\n",
    "1. **Input**: Original question, model responses, and search results\n",
    "2. **Synthesis Prompt**: Structures the final response to include:\n",
    "   - Main analysis with citations\n",
    "   - Common findings across models\n",
    "   - Unique insights from each model\n",
    "   - Source references\n",
    "3. **Output**: Comprehensive, well-cited answer that directly addresses the user's query\n",
    "\n",
    "The goal is to provide a balanced perspective that leverages the strengths of each AI model while maintaining clarity and accuracy."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Async function to synthesize responses from all models into a final answer\n",
    "async def synthesize_results(state: AgentState) -> dict:\n",
    "    \"\"\"Combine analyses from all models into a single, comprehensive answer.\"\"\"\n",
    "    original_question = state[\"messages\"][0].content\n",
    "    model_responses = state[\"model_responses\"]\n",
    "    search_results = state[\"aggregated_search_results\"]\n",
    "\n",
    "    # Define a prompt for synthesizing the final answer\n",
    "    synthesis_prompt = f\"\"\"Task: Synthesize multiple AI models' analyses into a single, coherent response.\n",
    "\n",
    "    Original Question: {original_question}\n",
    "\n",
    "    Different AI Models' Analyses:\n",
    "    {model_responses}\n",
    "\n",
    "    Original Search Results for Reference:\n",
    "    {search_results}\n",
    "\n",
    "    Please create a final response that:\n",
    "    1. Synthesizes the key points where models agree\n",
    "    2. Acknowledges any differences in models' interpretations\n",
    "    3. Maintains accurate citations throughout\n",
    "    4. Provides a unified, coherent answer that best serves the user's question\n",
    "\n",
    "    Structure your response as follows:\n",
    "    1. Main Answer (with citations)\n",
    "    2. Points of Agreement Between Models\n",
    "    3. Notable Differences or Additional Insights (if any)\n",
    "    4. Sources Used (list of all cited URLs)\n",
    "    \"\"\"\n",
    "    synthesizer = ChatOpenAI(model=\"gpt-4o\", temperature=0)  # Use GPT-4 for synthesis\n",
    "    final_response = await synthesizer.ainvoke([HumanMessage(content=synthesis_prompt)])  # Generate the final response\n",
    "    \n",
    "    # Return the final synthesized answer\n",
    "    return {\n",
    "        \"final_answer\": final_response.content,\n",
    "        \"messages\": [AIMessage(content=final_response.content)]  # Add the response to message history\n",
    "    }\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "metadata": {},
   "outputs": [],
   "source": [
    "workflow = StateGraph(AgentState)\n",
    "\n",
    "# 노드 추가\n",
    "workflow.add_node(\"query_generator\", generate_queries)\n",
    "workflow.add_node(\"search_executor\", search_executor)\n",
    "workflow.add_node(\"aggregate_results\", aggregate_search_results)  # 새로운 노드\n",
    "workflow.add_node(\"model_analyzer\", model_analyzer)\n",
    "workflow.add_node(\"synthesizer\", synthesize_results)\n",
    "\n",
    "# 엣지 추가\n",
    "workflow.add_edge(START, \"query_generator\")\n",
    "workflow.add_conditional_edges(\n",
    "    \"query_generator\",\n",
    "    create_search_tasks,\n",
    "    {\"search_executor\": \"search_executor\"}\n",
    ")\n",
    "workflow.add_edge(\"search_executor\", \"aggregate_results\")\n",
    "workflow.add_conditional_edges(\n",
    "    \"aggregate_results\", \n",
    "    create_analysis_tasks,\n",
    "    {\"model_analyzer\": \"model_analyzer\"}\n",
    ")\n",
    "workflow.add_edge(\"model_analyzer\", \"synthesizer\")\n",
    "workflow.add_edge(\"synthesizer\", END)\n",
    "app=workflow.compile()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import Image, display\n",
    "from langchain_core.runnables.graph import MermaidDrawMethod\n",
    "\n",
    "display(\n",
    "    Image(\n",
    "        app.get_graph().draw_mermaid_png(\n",
    "            draw_method=MermaidDrawMethod.API,\n",
    "        )\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 83,
   "metadata": {},
   "outputs": [],
   "source": [
    "import nest_asyncio\n",
    "nest_asyncio.apply()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Helper Functions and Execution\n",
    "Functions to manage output display and execute the analysis pipeline with clear progress tracking.\n",
    "\n",
    "**Key Components**:\n",
    "1. Progress Tracking (```print_progress```):\n",
    "   - Visual progress indicators for each pipeline stage\n",
    "   - Emoji-based status updates for better readability\n",
    "   - Clear tracking of query generation, search execution, and model analyses\n",
    "2. Result Display (```display_final_result```):\n",
    "   - Markdown formatting for the final analysis\n",
    "   - Structured presentation of synthesized results\n",
    "   - Clean citation and source reference display\n",
    "\n",
    "**Pipeline Execution**\n",
    "The execution process streams results in real-time, providing visibility into each stage of the analysis while maintaining the final result for formatted display.\n",
    "\n",
    "This approach ensures both transparency during processing and a professionally formatted final output."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import Markdown, display\n",
    "\n",
    "def print_progress(event: dict):\n",
    "    \"\"\"Print workflow progress in a visually appealing format\"\"\"\n",
    "    if \"query_generator\" in event:\n",
    "        print(\"\\n🔍 Generated Search Queries:\")\n",
    "        for query in event[\"query_generator\"][\"search_queries\"]:\n",
    "            print(f\"  • {query}\")\n",
    "    \n",
    "    elif \"search_executor\" in event:\n",
    "        query = list(event[\"search_executor\"][\"raw_search_results\"].keys())[0]\n",
    "        print(f\"\\n🔎 Completed search for: {query}\")\n",
    "    \n",
    "    elif \"aggregate_results\" in event:\n",
    "        print(\"\\n📑 Search results aggregated and formatted\")\n",
    "    \n",
    "    elif \"model_analyzer\" in event:\n",
    "        model_name = list(event[\"model_analyzer\"][\"model_responses\"].keys())[0]\n",
    "        print(f\"\\n🤖 Analysis from {model_name} completed\")\n",
    "    \n",
    "    elif \"synthesizer\" in event:\n",
    "        print(\"\\n✨ Final synthesized answer generated\")\n",
    "\n",
    "\n",
    "def display_final_result(final_response: str):\n",
    "    \"\"\"Display final result in markdown format\"\"\"\n",
    "    markdown_text = f\"\"\"\n",
    "    # Analysis Results\n",
    "\n",
    "    ## Final Answer\n",
    "    {final_response}\n",
    "    \"\"\"\n",
    "    display(Markdown(markdown_text))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Generated queries: ['Tesla Q1 2025 business strategy analysis', 'Tesla 2025 first quarter financial performance and outlook', 'Tesla strategic initiatives Q1 2025', 'Tesla business growth and challenges Q1 2025', 'Tesla Q1 2025 market expansion and innovation plans']\n",
      "\n",
      "🔍 Generated Search Queries:\n",
      "  • Tesla Q1 2025 business strategy analysis\n",
      "  • Tesla 2025 first quarter financial performance and outlook\n",
      "  • Tesla strategic initiatives Q1 2025\n",
      "  • Tesla business growth and challenges Q1 2025\n",
      "  • Tesla Q1 2025 market expansion and innovation plans\n",
      "Searching for: Tesla Q1 2025 business strategy analysis\n",
      "Searching for: Tesla 2025 first quarter financial performance and outlook\n",
      "Searching for: Tesla strategic initiatives Q1 2025\n",
      "Searching for: Tesla business growth and challenges Q1 2025\n",
      "Searching for: Tesla Q1 2025 market expansion and innovation plans\n",
      "\n",
      "🔎 Completed search for: Tesla 2025 first quarter financial performance and outlook\n",
      "\n",
      "🔎 Completed search for: Tesla business growth and challenges Q1 2025\n",
      "\n",
      "🔎 Completed search for: Tesla Q1 2025 market expansion and innovation plans\n",
      "\n",
      "🔎 Completed search for: Tesla strategic initiatives Q1 2025\n",
      "\n",
      "🔎 Completed search for: Tesla Q1 2025 business strategy analysis\n",
      "\n",
      "📑 Search results aggregated and formatted\n",
      "\n",
      "🤖 Analysis from gemini-1.5-flash completed\n",
      "\n",
      "🤖 Analysis from claude-haiku completed\n",
      "\n",
      "🤖 Analysis from gpt-4o-mini completed\n",
      "\n",
      "✨ Final synthesized answer generated\n"
     ]
    },
    {
     "data": {
      "text/markdown": [
       "\n",
       "# Analysis Results\n",
       "\n",
       "## Final Answer\n",
       "### Main Answer\n",
       "\n",
       "As of Q1 2025, Tesla's business strategy is focused on expanding its market presence through several key initiatives. A major component of this strategy is the planned launch of an affordable electric vehicle (EV) priced at approximately $25,000. This initiative aims to broaden Tesla's customer base and increase vehicle sales volume significantly [1][2][3]. Tesla is also targeting an annual production capacity of 100 GWh by the end of fiscal year 2025, with energy deployments already reaching 31.4 GWh in fiscal year 2024 [1][3]. \n",
       "\n",
       "In addition to vehicle affordability, Tesla is emphasizing cost reductions and advancements in artificial intelligence (AI) and autonomous vehicle (AV) technologies. The company is exploring new revenue streams through innovations such as robotaxis and Optimus robots, reflecting a broader vision for sustainable automotive solutions [1][2][3]. Financially, Tesla has reported strong earnings and forecasts up to 30% growth in vehicle sales for the year, supported by the success of the Cybertruck and declining material costs [1][2].\n",
       "\n",
       "### Points of Agreement Between Models\n",
       "\n",
       "1. **Affordable EV Launch**: All models agree that Tesla's strategy includes launching a $25,000 EV to expand its market reach and increase sales volume [1][2][3].\n",
       "   \n",
       "2. **Production and Capacity Goals**: There is consensus on Tesla's target of achieving 100 GWh in annual production by FY25, with significant energy deployments already underway [1][3].\n",
       "\n",
       "3. **Focus on AI and AV Technologies**: The models highlight Tesla's strategic focus on expanding its AI and AV capabilities, including the development of robotaxis and Optimus robots [1][2][3].\n",
       "\n",
       "4. **Financial Outlook**: All models note Tesla's optimistic financial outlook, with predictions of 20-30% growth in vehicle sales for 2025, contingent on the successful introduction of the lower-cost vehicle [1][2].\n",
       "\n",
       "### Notable Differences or Additional Insights\n",
       "\n",
       "- **Stock Valuation Concerns**: Some models, such as 'gpt-4o-mini' and 'claude-haiku', mention concerns about Tesla's stock valuation potentially being disconnected from its business fundamentals, attributing a premium to Elon Musk's leadership [3]. This aspect is less emphasized in 'gemini-1.5-flash'.\n",
       "\n",
       "- **Market Expansion**: While all models discuss Tesla's market expansion efforts, 'claude-haiku' specifically highlights the competitive landscape with other EV manufacturers like BYD and Volkswagen, which adds an element of uncertainty to Tesla's growth strategy [1].\n",
       "\n",
       "### Sources Used\n",
       "\n",
       "1. [Seeking Alpha](https://seekingalpha.com/article/4749379-how-tesla-plans-to-lead-in-2025)\n",
       "2. [Morningstar](https://www.morningstar.com/company-reports/1258674-teslas-strategic-focus-shifts-to-development-of-affordable-vehicles-cost-reductions-and-ai)\n",
       "3. [Investing.com](https://www.investing.com/news/stock-market-news/three-themes-to-shape-teslas-outlook-in-2025-says-barclays-3813602)\n",
       "    "
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# write question here\n",
    "question = \"Analyze Tesla's Q1 2025 Business Strategy Status and Outlook\"\n",
    "inputs = {\n",
    "    \"messages\": [HumanMessage(content=question)]\n",
    "}\n",
    "\n",
    "# Execution code\n",
    "final_result = None\n",
    "async for event in app.astream(inputs):\n",
    "    print_progress(event)\n",
    "    if \"synthesizer\" in event:\n",
    "        final_result = event[\"synthesizer\"][\"final_answer\"]\n",
    "\n",
    "if final_result:\n",
    "    display_final_result(final_result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-opentutorial-RXtDr8w5-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
